/*!********************************************************************

 Audacity: A Digital Audio Editor

 @file AccessibleLinksFormatter.h
 @brief Define a helper class to format text with link in a way, accessible to VI users.

 Dmitry Vedenko
 **********************************************************************/

#include "AccessibleLinksFormatter.h"

#include "ShuttleGui.h"

#include <unordered_map>
#include <algorithm>
#include <memory>

#include "BasicUI.h"

#include <wx/hyperlink.h>

#ifdef __WXGTK__
#include <wx/stattext.h>
#endif

namespace {
size_t OffsetPosition(size_t position, size_t length)
{
    if (position == wxString::npos) {
        return wxString::npos;
    }

    return position + length;
}
}

AccessibleLinksFormatter::AccessibleLinksFormatter(TranslatableString message)
    : mMessage(std::move(message))
{
}

AccessibleLinksFormatter& AccessibleLinksFormatter::FormatLink(
    wxString placeholder, TranslatableString value, std::string targetURL)
{
    mFormatArguments.push_back({
        std::move(placeholder),
        std::move(value),
        {},
        std::move(targetURL)
    });

    return *this;
}

AccessibleLinksFormatter& AccessibleLinksFormatter::FormatLink(
    wxString placeholder, TranslatableString value,
    LinkClickedHandler handler)
{
    mFormatArguments.push_back({
        std::move(placeholder),
        std::move(value),
        std::move(handler),
        {}
    });

    return *this;
}

void AccessibleLinksFormatter::Populate(ShuttleGui& S) const
{
    // Just add the text, if there are no links to format
    if (mFormatArguments.empty()) {
        S.AddFixedText(mMessage);
        return;
    }

#ifdef __WXGTK__
    // Non empty label is required, as wxHyperlinkCtrl would assert otherwise
    std::unique_ptr<wxHyperlinkCtrl> tempHyperlink
        = std::make_unique<wxHyperlinkCtrl>(S.GetParent(), wxID_ANY, wxT("temp"), wxString());

    const wxColour hyperlinkColour = tempHyperlink->GetNormalColour();

    tempHyperlink.reset();
#endif

    wxString translated = mMessage.Translation();

    std::vector<ProcessedArgument> processedArguments
        =ProcessArguments(translated);

    if (processedArguments.empty()) {
        S.AddFixedText(mMessage);
        return;
    }

    const int borderSize = S.GetBorder();

    S.StartHorizontalLay(wxEXPAND);
    {
        S.SetBorder(0);
        S.AddSpace(borderSize);

        S.StartWrapLay(wxEXPAND, 1);
        {
            size_t currentPosition = 0;

            for (const ProcessedArgument& processedArgument : processedArguments) {
                const FormatArgument* argument = processedArgument.Argument;

                // Add everything between currentPosition and PlaceholderPosition

                if (currentPosition != processedArgument.PlaceholderPosition) {
                    const size_t substrLength
                        =processedArgument.PlaceholderPosition - currentPosition;

                    S.Prop(0).AddFixedText(
                        Verbatim(translated.substr(currentPosition, substrLength)));
                }

                // Add hyperlink
#ifndef __WXGTK__
                const auto value = argument->Value.Translation();
                // On macOS wx refuses to create wxHyperlinkCtrl with an empty value
                if (!value.empty()) {
                    wxHyperlinkCtrl* hyperlink = safenew wxHyperlinkCtrl(
                        S.GetParent(), wxID_ANY, argument->Value.Translation(),
                        argument->TargetURL);

                    hyperlink->Bind(
                        wxEVT_HYPERLINK,
                        [handler = argument->Handler,
                         url = argument->TargetURL](wxHyperlinkEvent& evt)
                    {
                        if (handler) {
                            handler();
                        } else if (!url.empty()) {
                            BasicUI::OpenInDefaultBrowser(url);
                        }
                    });

                    S.AddWindow(hyperlink, wxALIGN_TOP | wxALIGN_LEFT);
                }
#else
                wxStaticText* hyperlink = S.AddVariableText(argument->Value);

                hyperlink->SetFont(hyperlink->GetFont().Underlined());
                hyperlink->SetForegroundColour(hyperlinkColour);
                hyperlink->SetCursor(wxCURSOR_HAND);

                hyperlink->Bind(wxEVT_LEFT_UP, [handler = argument->Handler, url = argument->TargetURL](wxEvent&) {
                    if (handler) {
                        handler();
                    } else if (!url.empty()) {
                        BasicUI::OpenInDefaultBrowser(url);
                    }
                });
#endif
                // Update the currentPostion to the first symbol after the
                // Placeholder

                currentPosition = OffsetPosition(
                    processedArgument.PlaceholderPosition,
                    argument->Placeholder.Length());

                if (currentPosition >= translated.Length()) {
                    break;
                }
            }

            if (currentPosition < translated.Length()) {
                S.AddFixedText(Verbatim(translated.substr(currentPosition)));
            }
        }
        S.EndWrapLay();
    }
    S.EndHorizontalLay();

    S.SetBorder(borderSize);
}

std::vector<AccessibleLinksFormatter::ProcessedArgument>
AccessibleLinksFormatter::ProcessArguments(wxString translatedMessage) const
{
    std::vector<ProcessedArgument> result;
    result.reserve(mFormatArguments.size());
    // Arguments with the same placeholder are processed left-to-right.
    // Lets track the last known position of the placeholder
    std::unordered_map<wxString, size_t> knownPlaceholderPosition;

    for (const FormatArgument& argument : mFormatArguments) {
        auto it = knownPlaceholderPosition.find(argument.Placeholder);

        const size_t startingPosition
            =it != knownPlaceholderPosition.end()
              ? OffsetPosition(it->second, argument.Placeholder.length())
              : 0;

        const size_t placeholderPosition
            =startingPosition == wxString::npos
              ? wxString::npos
              : translatedMessage.find(argument.Placeholder, startingPosition);

        knownPlaceholderPosition[argument.Placeholder] = placeholderPosition;

        if (placeholderPosition != wxString::npos) {
            result.emplace_back(
                ProcessedArgument { &argument, placeholderPosition });
        }
    }

    std::sort(
        result.begin(), result.end(),
        [](const ProcessedArgument& lhs, const ProcessedArgument& rhs) {
        return lhs.PlaceholderPosition < rhs.PlaceholderPosition;
    });

    return result;
}
